Split out encoding from `core::resolver` to a submodule
authorAlex Crichton <alex@alexcrichton.com>
Thu, 16 Oct 2014 18:25:05 +0000 (11:25 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 22 Oct 2014 18:29:24 +0000 (11:29 -0700)
Same exported hierarchy, just some internal orgainzational changes.

src/cargo/core/resolver.rs [deleted file]
src/cargo/core/resolver/encode.rs [new file with mode: 0644]
src/cargo/core/resolver/mod.rs [new file with mode: 0644]

diff --git a/src/cargo/core/resolver.rs b/src/cargo/core/resolver.rs
deleted file mode 100644 (file)
index 2e846e3..0000000
+++ /dev/null
@@ -1,741 +0,0 @@
-use std::collections::hashmap::{HashMap, HashSet, Occupied, Vacant};
-use std::fmt;
-
-use regex::Regex;
-use semver;
-use serialize::{Encodable, Encoder, Decodable, Decoder};
-
-use core::{PackageId, Registry, SourceId, Summary, Dependency};
-use core::PackageIdSpec;
-use util::{CargoResult, Graph, human, internal, ChainError};
-use util::profile;
-use util::graph::{Nodes, Edges};
-
-/// Result of the `resolve` function.
-#[deriving(PartialEq, Eq)]
-pub struct Resolve {
-    graph: Graph<PackageId>,
-    features: HashMap<PackageId, HashSet<String>>,
-    root: PackageId
-}
-
-pub enum ResolveMethod<'a> {
-    ResolveEverything,
-    ResolveRequired(/* dev_deps = */ bool,
-                    /* features = */ &'a [String],
-                    /* uses_default_features = */ bool),
-}
-
-#[deriving(Encodable, Decodable, Show)]
-pub struct EncodableResolve {
-    package: Option<Vec<EncodableDependency>>,
-    root: EncodableDependency
-}
-
-impl EncodableResolve {
-    pub fn to_resolve(&self, default: &SourceId) -> CargoResult<Resolve> {
-        let mut g = Graph::new();
-
-        try!(add_pkg_to_graph(&mut g, &self.root, default));
-
-        match self.package {
-            Some(ref packages) => {
-                for dep in packages.iter() {
-                    try!(add_pkg_to_graph(&mut g, dep, default));
-                }
-            }
-            None => {}
-        }
-
-        let root = self.root.to_package_id(default);
-        Ok(Resolve { graph: g, root: try!(root), features: HashMap::new() })
-    }
-}
-
-fn add_pkg_to_graph(g: &mut Graph<PackageId>,
-                    dep: &EncodableDependency,
-                    default: &SourceId)
-                    -> CargoResult<()>
-{
-    let package_id = try!(dep.to_package_id(default));
-    g.add(package_id.clone(), []);
-
-    match dep.dependencies {
-        Some(ref deps) => {
-            for edge in deps.iter() {
-                g.link(package_id.clone(), try!(edge.to_package_id(default)));
-            }
-        },
-        _ => ()
-    };
-
-    Ok(())
-}
-
-#[deriving(Encodable, Decodable, Show, PartialOrd, Ord, PartialEq, Eq)]
-pub struct EncodableDependency {
-    name: String,
-    version: String,
-    source: Option<SourceId>,
-    dependencies: Option<Vec<EncodablePackageId>>
-}
-
-impl EncodableDependency {
-    fn to_package_id(&self, default_source: &SourceId) -> CargoResult<PackageId> {
-        PackageId::new(
-            self.name.as_slice(),
-            self.version.as_slice(),
-            self.source.as_ref().unwrap_or(default_source))
-    }
-}
-
-#[deriving(Show, PartialOrd, Ord, PartialEq, Eq)]
-pub struct EncodablePackageId {
-    name: String,
-    version: String,
-    source: Option<SourceId>
-}
-
-impl<E, S: Encoder<E>> Encodable<S, E> for EncodablePackageId {
-    fn encode(&self, s: &mut S) -> Result<(), E> {
-        let mut out = format!("{} {}", self.name, self.version);
-        if let Some(ref s) = self.source {
-            out.push_str(format!(" ({})", s.to_url()).as_slice());
-        }
-        out.encode(s)
-    }
-}
-
-impl<E, D: Decoder<E>> Decodable<D, E> for EncodablePackageId {
-    fn decode(d: &mut D) -> Result<EncodablePackageId, E> {
-        let string: String = raw_try!(Decodable::decode(d));
-        let regex = Regex::new(r"^([^ ]+) ([^ ]+)(?: \(([^\)]+)\))?$").unwrap();
-        let captures = regex.captures(string.as_slice())
-                            .expect("invalid serialized PackageId");
-
-        let name = captures.at(1);
-        let version = captures.at(2);
-
-        let source = captures.at(3);
-
-        let source_id = if source == "" {
-            None
-        } else {
-            Some(SourceId::from_url(source.to_string()))
-        };
-
-        Ok(EncodablePackageId {
-            name: name.to_string(),
-            version: version.to_string(),
-            source: source_id
-        })
-    }
-}
-
-impl EncodablePackageId {
-    fn to_package_id(&self, default_source: &SourceId) -> CargoResult<PackageId> {
-        PackageId::new(
-            self.name.as_slice(),
-            self.version.as_slice(),
-            self.source.as_ref().unwrap_or(default_source))
-    }
-}
-
-impl<E, S: Encoder<E>> Encodable<S, E> for Resolve {
-    fn encode(&self, s: &mut S) -> Result<(), E> {
-        let mut ids: Vec<&PackageId> = self.graph.iter().collect();
-        ids.sort();
-
-        let encodable = ids.iter().filter_map(|&id| {
-            if self.root == *id { return None; }
-
-            Some(encodable_resolve_node(id, &self.root, &self.graph))
-        }).collect::<Vec<EncodableDependency>>();
-
-        EncodableResolve {
-            package: Some(encodable),
-            root: encodable_resolve_node(&self.root, &self.root, &self.graph)
-        }.encode(s)
-    }
-}
-
-fn encodable_resolve_node(id: &PackageId, root: &PackageId,
-                          graph: &Graph<PackageId>) -> EncodableDependency {
-    let deps = graph.edges(id).map(|edge| {
-        let mut deps = edge.map(|e| {
-            encodable_package_id(e, root)
-        }).collect::<Vec<EncodablePackageId>>();
-        deps.sort();
-        deps
-    });
-
-    let source = if id.get_source_id() == root.get_source_id() {
-        None
-    } else {
-        Some(id.get_source_id().clone())
-    };
-
-    EncodableDependency {
-        name: id.get_name().to_string(),
-        version: id.get_version().to_string(),
-        source: source,
-        dependencies: deps,
-    }
-}
-
-fn encodable_package_id(id: &PackageId, root: &PackageId) -> EncodablePackageId {
-    let source = if id.get_source_id() == root.get_source_id() {
-        None
-    } else {
-        Some(id.get_source_id().clone())
-    };
-    EncodablePackageId {
-        name: id.get_name().to_string(),
-        version: id.get_version().to_string(),
-        source: source,
-    }
-}
-
-impl Resolve {
-    fn new(root: PackageId) -> Resolve {
-        let mut g = Graph::new();
-        g.add(root.clone(), []);
-        Resolve { graph: g, root: root, features: HashMap::new() }
-    }
-
-    pub fn iter(&self) -> Nodes<PackageId> {
-        self.graph.iter()
-    }
-
-    pub fn root(&self) -> &PackageId { &self.root }
-
-    pub fn deps(&self, pkg: &PackageId) -> Option<Edges<PackageId>> {
-        self.graph.edges(pkg)
-    }
-
-    pub fn query(&self, spec: &str) -> CargoResult<&PackageId> {
-        let spec = try!(PackageIdSpec::parse(spec).chain_error(|| {
-            human(format!("invalid package id specification: `{}`", spec))
-        }));
-        let mut ids = self.iter().filter(|p| spec.matches(*p));
-        let ret = match ids.next() {
-            Some(id) => id,
-            None => return Err(human(format!("package id specification `{}` \
-                                              matched no packages", spec))),
-        };
-        return match ids.next() {
-            Some(other) => {
-                let mut msg = format!("There are multiple `{}` packages in \
-                                       your project, and the specification \
-                                       `{}` is ambiguous.\n\
-                                       Please re-run this command \
-                                       with `-p <spec>` where `<spec>` is one \
-                                       of the following:",
-                                      spec.get_name(), spec);
-                let mut vec = vec![ret, other];
-                vec.extend(ids);
-                minimize(&mut msg, vec, &spec);
-                Err(human(msg))
-            }
-            None => Ok(ret)
-        };
-
-        fn minimize(msg: &mut String,
-                    ids: Vec<&PackageId>,
-                    spec: &PackageIdSpec) {
-            let mut version_cnt = HashMap::new();
-            for id in ids.iter() {
-                let slot = match version_cnt.entry(id.get_version()) {
-                    Occupied(e) => e.into_mut(),
-                    Vacant(e) => e.set(0u),
-                };
-                *slot += 1;
-            }
-            for id in ids.iter() {
-                if version_cnt[id.get_version()] == 1 {
-                    msg.push_str(format!("\n  {}:{}", spec.get_name(),
-                                 id.get_version()).as_slice());
-                } else {
-                    msg.push_str(format!("\n  {}",
-                                         PackageIdSpec::from_package_id(*id))
-                                        .as_slice());
-                }
-            }
-        }
-    }
-
-    pub fn features(&self, pkg: &PackageId) -> Option<&HashSet<String>> {
-        self.features.find(pkg)
-    }
-}
-
-impl fmt::Show for Resolve {
-    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
-        self.graph.fmt(fmt)
-    }
-}
-
-struct Context<'a, R:'a> {
-    registry: &'a mut R,
-    resolve: Resolve,
-    // cycle detection
-    visited: HashSet<PackageId>,
-
-    // Try not to re-resolve too much
-    resolved: HashMap<PackageId, HashSet<String>>,
-
-    // Eventually, we will have smarter logic for checking for conflicts in the
-    // resolve, but without the registry, conflicts should not exist in
-    // practice, so this is just a sanity check.
-    seen: HashMap<(String, SourceId), semver::Version>,
-}
-
-impl<'a, R: Registry> Context<'a, R> {
-    fn new(registry: &'a mut R, root: PackageId) -> Context<'a, R> {
-        Context {
-            registry: registry,
-            resolve: Resolve::new(root),
-            seen: HashMap::new(),
-            visited: HashSet::new(),
-            resolved: HashMap::new(),
-        }
-    }
-}
-
-/// Builds the list of all packages required to build the first argument.
-pub fn resolve<R: Registry>(summary: &Summary, method: ResolveMethod,
-                            registry: &mut R) -> CargoResult<Resolve> {
-    log!(5, "resolve; summary={}", summary);
-    let _p = profile::start(format!("resolving: {}", summary));
-
-    let mut context = Context::new(registry, summary.get_package_id().clone());
-    context.seen.insert((summary.get_name().to_string(),
-                         summary.get_source_id().clone()),
-                        summary.get_version().clone());
-    try!(resolve_deps(summary, method, &mut context));
-    log!(5, "  result={}", context.resolve);
-    Ok(context.resolve)
-}
-
-fn resolve_deps<'a, R: Registry>(parent: &Summary,
-                                 method: ResolveMethod,
-                                 ctx: &mut Context<'a, R>)
-                                 -> CargoResult<()> {
-    let (deps, features) = try!(resolve_features(parent, method, ctx));
-
-    // Recursively resolve all dependencies
-    for &dep in deps.iter() {
-        if !match ctx.resolved.entry(parent.get_package_id().clone()) {
-            Occupied(entry) => entry.into_mut(),
-            Vacant(entry) => entry.set(HashSet::new()),
-        }.insert(dep.get_name().to_string()) {
-            continue
-        }
-
-        let pkgs = try!(ctx.registry.query(dep));
-        if pkgs.is_empty() {
-            return Err(human(format!("No package named `{}` found \
-                                      (required by `{}`).\n\
-                                      Location searched: {}\n\
-                                      Version required: {}",
-                                     dep.get_name(),
-                                     parent.get_package_id().get_name(),
-                                     dep.get_source_id(),
-                                     dep.get_version_req())));
-        } else if pkgs.len() > 1 {
-            return Err(internal(format!("At the moment, Cargo only supports a \
-                single source for a particular package name ({}).", dep)));
-        }
-
-        let summary = &pkgs[0];
-        let name = summary.get_name().to_string();
-        let source_id = summary.get_source_id().clone();
-        let version = summary.get_version();
-
-        ctx.resolve.graph.link(parent.get_package_id().clone(),
-                               summary.get_package_id().clone());
-
-        let found = match ctx.seen.find(&(name.clone(), source_id.clone())) {
-            Some(v) if v == version => true,
-            Some(..) => {
-                return Err(human(format!("Cargo found multiple copies of {} in \
-                                          {}. This is not currently supported",
-                                         summary.get_name(),
-                                         summary.get_source_id())));
-            }
-            None => false,
-        };
-        if !found {
-            ctx.seen.insert((name, source_id), version.clone());
-            ctx.resolve.graph.add(summary.get_package_id().clone(), []);
-        }
-
-        // Dependency graphs are required to be a DAG. Non-transitive
-        // dependencies (dev-deps), however, can never introduce a cycle, so we
-        // skip them.
-        if dep.is_transitive() &&
-           !ctx.visited.insert(summary.get_package_id().clone()) {
-            return Err(human(format!("Cyclic package dependency: package `{}` \
-                                      depends on itself",
-                                     summary.get_package_id())))
-        }
-
-        // The list of enabled features for this dependency are both those
-        // listed in the dependency itself as well as any of our own features
-        // which enabled a feature of this package.
-        let features = features.find_equiv(&dep.get_name())
-                               .map(|v| v.as_slice())
-                               .unwrap_or(&[]);
-        try!(resolve_deps(summary,
-                          ResolveRequired(false, features,
-                                          dep.uses_default_features()),
-                          ctx));
-        if dep.is_transitive() {
-            ctx.visited.remove(summary.get_package_id());
-        }
-    }
-
-    Ok(())
-}
-
-fn resolve_features<'a, R>(parent: &'a Summary, method: ResolveMethod,
-                           ctx: &mut Context<R>)
-                           -> CargoResult<(Vec<&'a Dependency>,
-                                           HashMap<&'a str, Vec<String>>)> {
-    let dev_deps = match method {
-        ResolveEverything => true,
-        ResolveRequired(dev_deps, _, _) => dev_deps,
-    };
-
-    // First, filter by dev-dependencies
-    let deps = parent.get_dependencies();
-    let deps = deps.iter().filter(|d| d.is_transitive() || dev_deps);
-
-    // Second, weed out optional dependencies, but not those required
-    let (mut feature_deps, used_features) = try!(build_features(parent, method));
-    let mut ret = HashMap::new();
-    let deps = deps.filter(|d| {
-        !d.is_optional() || feature_deps.contains_key_equiv(&d.get_name())
-    }).collect::<Vec<&Dependency>>();
-
-    // Next, sanitize all requested features by whitelisting all the requested
-    // features that correspond to optional dependencies
-    for dep in deps.iter() {
-        let mut base = feature_deps.pop_equiv(&dep.get_name())
-                                   .unwrap_or(Vec::new());
-        for feature in dep.get_features().iter() {
-            base.push(feature.clone());
-            if feature.as_slice().contains("/") {
-                return Err(human(format!("features in dependencies \
-                                          cannot enable features in \
-                                          other dependencies: `{}`",
-                                         feature)));
-            }
-        }
-        ret.insert(dep.get_name(), base);
-    }
-
-    // All features can only point to optional dependencies, in which case they
-    // should have all been weeded out by the above iteration. Any remaining
-    // features are bugs in that the package does not actually have those
-    // features.
-    if feature_deps.len() > 0 {
-        let unknown = feature_deps.keys().map(|s| s.as_slice())
-                                  .collect::<Vec<&str>>();
-        if unknown.len() > 0 {
-            let features = unknown.connect(", ");
-            return Err(human(format!("Package `{}` does not have these features: \
-                                      `{}`", parent.get_package_id(), features)))
-        }
-    }
-
-    // Record what list of features is active for this package.
-    {
-        let pkgid = parent.get_package_id().clone();
-        match ctx.resolve.features.entry(pkgid) {
-            Occupied(entry) => entry.into_mut(),
-            Vacant(entry) => entry.set(HashSet::new()),
-        }.extend(used_features.into_iter());
-    }
-
-    Ok((deps, ret))
-}
-
-// Returns a pair of (feature dependencies, all used features)
-//
-// The feature dependencies map is a mapping of package name to list of features
-// enabled. Each package should be enabled, and each package should have the
-// specified set of features enabled.
-//
-// The all used features set is the set of features which this local package had
-// enabled, which is later used when compiling to instruct the code what
-// features were enabled.
-fn build_features(s: &Summary, method: ResolveMethod)
-                  -> CargoResult<(HashMap<String, Vec<String>>, HashSet<String>)> {
-    let mut deps = HashMap::new();
-    let mut used = HashSet::new();
-    let mut visited = HashSet::new();
-    match method {
-        ResolveEverything => {
-            for key in s.get_features().keys() {
-                try!(add_feature(s, key.as_slice(), &mut deps, &mut used,
-                                 &mut visited));
-            }
-        }
-        ResolveRequired(_, requested_features, _) =>  {
-            for feat in requested_features.iter() {
-                try!(add_feature(s, feat.as_slice(), &mut deps, &mut used,
-                                 &mut visited));
-            }
-        }
-    }
-    match method {
-        ResolveEverything | ResolveRequired(_, _, true) => {
-            if s.get_features().find_equiv(&"default").is_some() &&
-               !visited.contains_equiv(&"default") {
-                try!(add_feature(s, "default", &mut deps, &mut used,
-                                 &mut visited));
-            }
-        }
-        _ => {}
-    }
-    return Ok((deps, used));
-
-    fn add_feature(s: &Summary, feat: &str,
-                   deps: &mut HashMap<String, Vec<String>>,
-                   used: &mut HashSet<String>,
-                   visited: &mut HashSet<String>) -> CargoResult<()> {
-        if feat.is_empty() { return Ok(()) }
-
-        // If this feature is of the form `foo/bar`, then we just lookup package
-        // `foo` and enable its feature `bar`. Otherwise this feature is of the
-        // form `foo` and we need to recurse to enable the feature `foo` for our
-        // own package, which may end up enabling more features or just enabling
-        // a dependency.
-        let mut parts = feat.splitn(1, '/');
-        let feat_or_package = parts.next().unwrap();
-        match parts.next() {
-            Some(feat) => {
-                let package = feat_or_package;
-                match deps.entry(package.to_string()) {
-                    Occupied(e) => e.into_mut(),
-                    Vacant(e) => e.set(Vec::new()),
-                }.push(feat.to_string());
-            }
-            None => {
-                let feat = feat_or_package;
-                if !visited.insert(feat.to_string()) {
-                    return Err(human(format!("Cyclic feature dependency: \
-                                              feature `{}` depends on itself",
-                                              feat)))
-                }
-                used.insert(feat.to_string());
-                match s.get_features().find_equiv(&feat) {
-                    Some(recursive) => {
-                        for f in recursive.iter() {
-                            try!(add_feature(s, f.as_slice(), deps, used,
-                                             visited));
-                        }
-                    }
-                    None => {
-                        match deps.entry(feat.to_string()) {
-                            Occupied(..) => {} // already activated
-                            Vacant(e) => { e.set(Vec::new()); }
-                        }
-                    }
-                }
-                visited.remove(&feat.to_string());
-            }
-        }
-        Ok(())
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use std::collections::HashMap;
-
-    use hamcrest::{assert_that, equal_to, contains};
-
-    use core::source::{SourceId, RegistryKind, GitKind};
-    use core::{Dependency, PackageId, Summary, Registry};
-    use util::{CargoResult, ToUrl};
-
-    fn resolve<R: Registry>(pkg: PackageId, deps: Vec<Dependency>,
-                            registry: &mut R)
-                            -> CargoResult<Vec<PackageId>> {
-        let summary = Summary::new(pkg, deps, HashMap::new()).unwrap();
-        let method = super::ResolveEverything;
-        Ok(try!(super::resolve(&summary, method,
-                               registry)).iter().map(|p| p.clone()).collect())
-    }
-
-    trait ToDep {
-        fn to_dep(self) -> Dependency;
-    }
-
-    impl ToDep for &'static str {
-        fn to_dep(self) -> Dependency {
-            let url = "http://example.com".to_url().unwrap();
-            let source_id = SourceId::new(RegistryKind, url);
-            Dependency::parse(self, Some("1.0.0"), &source_id).unwrap()
-        }
-    }
-
-    impl ToDep for Dependency {
-        fn to_dep(self) -> Dependency {
-            self
-        }
-    }
-
-    macro_rules! pkg(
-        ($name:expr => $($deps:expr),+) => ({
-            let d: Vec<Dependency> = vec!($($deps.to_dep()),+);
-
-            Summary::new(PackageId::new($name, "1.0.0", &registry_loc()).unwrap(),
-                         d, HashMap::new()).unwrap()
-        });
-
-        ($name:expr) => (
-            Summary::new(PackageId::new($name, "1.0.0", &registry_loc()).unwrap(),
-                         Vec::new(), HashMap::new()).unwrap()
-        )
-    )
-
-    fn registry_loc() -> SourceId {
-        let remote = "http://example.com".to_url().unwrap();
-        SourceId::new(RegistryKind, remote)
-    }
-
-    fn pkg(name: &str) -> Summary {
-        Summary::new(pkg_id(name), Vec::new(), HashMap::new()).unwrap()
-    }
-
-    fn pkg_id(name: &str) -> PackageId {
-        PackageId::new(name, "1.0.0", &registry_loc()).unwrap()
-    }
-
-    fn pkg_id_loc(name: &str, loc: &str) -> PackageId {
-        let remote = loc.to_url();
-        let source_id = SourceId::new(GitKind("master".to_string()),
-                                      remote.unwrap());
-
-        PackageId::new(name, "1.0.0", &source_id).unwrap()
-    }
-
-    fn pkg_loc(name: &str, loc: &str) -> Summary {
-        Summary::new(pkg_id_loc(name, loc), Vec::new(), HashMap::new()).unwrap()
-    }
-
-    fn dep(name: &str) -> Dependency {
-        let url = "http://example.com".to_url().unwrap();
-        let source_id = SourceId::new(RegistryKind, url);
-        Dependency::parse(name, Some("1.0.0"), &source_id).unwrap()
-    }
-
-    fn dep_loc(name: &str, location: &str) -> Dependency {
-        let url = location.to_url().unwrap();
-        let source_id = SourceId::new(GitKind("master".to_string()), url);
-        Dependency::parse(name, Some("1.0.0"), &source_id).unwrap()
-    }
-
-    fn registry(pkgs: Vec<Summary>) -> Vec<Summary> {
-        pkgs
-    }
-
-    fn names(names: &[&'static str]) -> Vec<PackageId> {
-        names.iter()
-            .map(|name| PackageId::new(*name, "1.0.0", &registry_loc()).unwrap())
-            .collect()
-    }
-
-    fn loc_names(names: &[(&'static str, &'static str)]) -> Vec<PackageId> {
-        names.iter()
-            .map(|&(name, loc)| pkg_id_loc(name, loc)).collect()
-    }
-
-    #[test]
-    pub fn test_resolving_empty_dependency_list() {
-        let res = resolve(pkg_id("root"), Vec::new(),
-                          &mut registry(vec!())).unwrap();
-
-        assert_that(&res, equal_to(&names(["root"])));
-    }
-
-    #[test]
-    pub fn test_resolving_only_package() {
-        let mut reg = registry(vec!(pkg("foo")));
-        let res = resolve(pkg_id("root"), vec![dep("foo")], &mut reg);
-
-        assert_that(&res.unwrap(), contains(names(["root", "foo"])).exactly());
-    }
-
-    #[test]
-    pub fn test_resolving_one_dep() {
-        let mut reg = registry(vec!(pkg("foo"), pkg("bar")));
-        let res = resolve(pkg_id("root"), vec![dep("foo")], &mut reg);
-
-        assert_that(&res.unwrap(), contains(names(["root", "foo"])).exactly());
-    }
-
-    #[test]
-    pub fn test_resolving_multiple_deps() {
-        let mut reg = registry(vec!(pkg!("foo"), pkg!("bar"), pkg!("baz")));
-        let res = resolve(pkg_id("root"), vec![dep("foo"), dep("baz")],
-                          &mut reg).unwrap();
-
-        assert_that(&res, contains(names(["root", "foo", "baz"])).exactly());
-    }
-
-    #[test]
-    pub fn test_resolving_transitive_deps() {
-        let mut reg = registry(vec!(pkg!("foo"), pkg!("bar" => "foo")));
-        let res = resolve(pkg_id("root"), vec![dep("bar")], &mut reg).unwrap();
-
-        assert_that(&res, contains(names(["root", "foo", "bar"])));
-    }
-
-    #[test]
-    pub fn test_resolving_common_transitive_deps() {
-        let mut reg = registry(vec!(pkg!("foo" => "bar"), pkg!("bar")));
-        let res = resolve(pkg_id("root"), vec![dep("foo"), dep("bar")],
-                          &mut reg).unwrap();
-
-        assert_that(&res, contains(names(["root", "foo", "bar"])));
-    }
-
-    #[test]
-    pub fn test_resolving_with_same_name() {
-        let list = vec![pkg_loc("foo", "http://first.example.com"),
-                        pkg_loc("bar", "http://second.example.com")];
-
-        let mut reg = registry(list);
-        let res = resolve(pkg_id("root"),
-                          vec![dep_loc("foo", "http://first.example.com"),
-                               dep_loc("bar", "http://second.example.com")],
-                          &mut reg);
-
-        let mut names = loc_names([("foo", "http://first.example.com"),
-                                   ("bar", "http://second.example.com")]);
-
-        names.push(pkg_id("root"));
-
-        assert_that(&res.unwrap(), contains(names).exactly());
-    }
-
-    #[test]
-    pub fn test_resolving_with_dev_deps() {
-        let mut reg = registry(vec!(
-            pkg!("foo" => "bar", dep("baz").transitive(false)),
-            pkg!("baz" => "bat", dep("bam").transitive(false)),
-            pkg!("bar"),
-            pkg!("bat")
-        ));
-
-        let res = resolve(pkg_id("root"),
-                          vec![dep("foo"), dep("baz").transitive(false)],
-                          &mut reg).unwrap();
-
-        assert_that(&res, contains(names(["root", "foo", "bar", "baz"])));
-    }
-}
diff --git a/src/cargo/core/resolver/encode.rs b/src/cargo/core/resolver/encode.rs
new file mode 100644 (file)
index 0000000..2eab82f
--- /dev/null
@@ -0,0 +1,179 @@
+use std::collections::HashMap;
+
+use regex::Regex;
+use serialize::{Encodable, Encoder, Decodable, Decoder};
+
+use core::{PackageId, SourceId};
+use util::{CargoResult, Graph};
+
+use super::Resolve;
+
+#[deriving(Encodable, Decodable, Show)]
+pub struct EncodableResolve {
+    package: Option<Vec<EncodableDependency>>,
+    root: EncodableDependency
+}
+
+impl EncodableResolve {
+    pub fn to_resolve(&self, default: &SourceId) -> CargoResult<Resolve> {
+        let mut g = Graph::new();
+
+        try!(add_pkg_to_graph(&mut g, &self.root, default));
+
+        match self.package {
+            Some(ref packages) => {
+                for dep in packages.iter() {
+                    try!(add_pkg_to_graph(&mut g, dep, default));
+                }
+            }
+            None => {}
+        }
+
+        let root = self.root.to_package_id(default);
+        Ok(Resolve { graph: g, root: try!(root), features: HashMap::new() })
+    }
+}
+
+fn add_pkg_to_graph(g: &mut Graph<PackageId>,
+                    dep: &EncodableDependency,
+                    default: &SourceId)
+                    -> CargoResult<()>
+{
+    let package_id = try!(dep.to_package_id(default));
+    g.add(package_id.clone(), []);
+
+    match dep.dependencies {
+        Some(ref deps) => {
+            for edge in deps.iter() {
+                g.link(package_id.clone(), try!(edge.to_package_id(default)));
+            }
+        },
+        _ => ()
+    };
+
+    Ok(())
+}
+
+#[deriving(Encodable, Decodable, Show, PartialOrd, Ord, PartialEq, Eq)]
+pub struct EncodableDependency {
+    name: String,
+    version: String,
+    source: Option<SourceId>,
+    dependencies: Option<Vec<EncodablePackageId>>
+}
+
+impl EncodableDependency {
+    fn to_package_id(&self, default_source: &SourceId) -> CargoResult<PackageId> {
+        PackageId::new(
+            self.name.as_slice(),
+            self.version.as_slice(),
+            self.source.as_ref().unwrap_or(default_source))
+    }
+}
+
+#[deriving(Show, PartialOrd, Ord, PartialEq, Eq)]
+pub struct EncodablePackageId {
+    name: String,
+    version: String,
+    source: Option<SourceId>
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for EncodablePackageId {
+    fn encode(&self, s: &mut S) -> Result<(), E> {
+        let mut out = format!("{} {}", self.name, self.version);
+        if let Some(ref s) = self.source {
+            out.push_str(format!(" ({})", s.to_url()).as_slice());
+        }
+        out.encode(s)
+    }
+}
+
+impl<E, D: Decoder<E>> Decodable<D, E> for EncodablePackageId {
+    fn decode(d: &mut D) -> Result<EncodablePackageId, E> {
+        let string: String = raw_try!(Decodable::decode(d));
+        let regex = Regex::new(r"^([^ ]+) ([^ ]+)(?: \(([^\)]+)\))?$").unwrap();
+        let captures = regex.captures(string.as_slice())
+                            .expect("invalid serialized PackageId");
+
+        let name = captures.at(1);
+        let version = captures.at(2);
+
+        let source = captures.at(3);
+
+        let source_id = if source == "" {
+            None
+        } else {
+            Some(SourceId::from_url(source.to_string()))
+        };
+
+        Ok(EncodablePackageId {
+            name: name.to_string(),
+            version: version.to_string(),
+            source: source_id
+        })
+    }
+}
+
+impl EncodablePackageId {
+    fn to_package_id(&self, default_source: &SourceId) -> CargoResult<PackageId> {
+        PackageId::new(
+            self.name.as_slice(),
+            self.version.as_slice(),
+            self.source.as_ref().unwrap_or(default_source))
+    }
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for Resolve {
+    fn encode(&self, s: &mut S) -> Result<(), E> {
+        let mut ids: Vec<&PackageId> = self.graph.iter().collect();
+        ids.sort();
+
+        let encodable = ids.iter().filter_map(|&id| {
+            if self.root == *id { return None; }
+
+            Some(encodable_resolve_node(id, &self.root, &self.graph))
+        }).collect::<Vec<EncodableDependency>>();
+
+        EncodableResolve {
+            package: Some(encodable),
+            root: encodable_resolve_node(&self.root, &self.root, &self.graph)
+        }.encode(s)
+    }
+}
+
+fn encodable_resolve_node(id: &PackageId, root: &PackageId,
+                          graph: &Graph<PackageId>) -> EncodableDependency {
+    let deps = graph.edges(id).map(|edge| {
+        let mut deps = edge.map(|e| {
+            encodable_package_id(e, root)
+        }).collect::<Vec<EncodablePackageId>>();
+        deps.sort();
+        deps
+    });
+
+    let source = if id.get_source_id() == root.get_source_id() {
+        None
+    } else {
+        Some(id.get_source_id().clone())
+    };
+
+    EncodableDependency {
+        name: id.get_name().to_string(),
+        version: id.get_version().to_string(),
+        source: source,
+        dependencies: deps,
+    }
+}
+
+fn encodable_package_id(id: &PackageId, root: &PackageId) -> EncodablePackageId {
+    let source = if id.get_source_id() == root.get_source_id() {
+        None
+    } else {
+        Some(id.get_source_id().clone())
+    };
+    EncodablePackageId {
+        name: id.get_name().to_string(),
+        version: id.get_version().to_string(),
+        source: source,
+    }
+}
diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs
new file mode 100644 (file)
index 0000000..dbec2bf
--- /dev/null
@@ -0,0 +1,576 @@
+use std::collections::hashmap::{HashMap, HashSet, Occupied, Vacant};
+use std::fmt;
+
+use core::{PackageId, Registry, SourceId, Summary, Dependency};
+use core::PackageIdSpec;
+use util::{CargoResult, Graph, human, internal, ChainError};
+use util::profile;
+use util::graph::{Nodes, Edges};
+
+pub use self::encode::{EncodableResolve, EncodableDependency, EncodablePackageId};
+
+mod encode;
+
+/// Represents a fully resolved package dependency graph. Each node in the graph
+/// is a package and edges represent dependencies between packages.
+///
+/// Each instance of `Resolve` also understands the full set of features used
+/// for each package as well as what the root package is.
+#[deriving(PartialEq, Eq)]
+pub struct Resolve {
+    graph: Graph<PackageId>,
+    features: HashMap<PackageId, HashSet<String>>,
+    root: PackageId
+}
+
+pub enum ResolveMethod<'a> {
+    ResolveEverything,
+    ResolveRequired(/* dev_deps = */ bool,
+                    /* features = */ &'a [String],
+                    /* uses_default_features = */ bool),
+}
+
+impl Resolve {
+    fn new(root: PackageId) -> Resolve {
+        let mut g = Graph::new();
+        g.add(root.clone(), []);
+        Resolve { graph: g, root: root, features: HashMap::new() }
+    }
+
+    pub fn iter(&self) -> Nodes<PackageId> {
+        self.graph.iter()
+    }
+
+    pub fn root(&self) -> &PackageId { &self.root }
+
+    pub fn deps(&self, pkg: &PackageId) -> Option<Edges<PackageId>> {
+        self.graph.edges(pkg)
+    }
+
+    pub fn query(&self, spec: &str) -> CargoResult<&PackageId> {
+        let spec = try!(PackageIdSpec::parse(spec).chain_error(|| {
+            human(format!("invalid package id specification: `{}`", spec))
+        }));
+        let mut ids = self.iter().filter(|p| spec.matches(*p));
+        let ret = match ids.next() {
+            Some(id) => id,
+            None => return Err(human(format!("package id specification `{}` \
+                                              matched no packages", spec))),
+        };
+        return match ids.next() {
+            Some(other) => {
+                let mut msg = format!("There are multiple `{}` packages in \
+                                       your project, and the specification \
+                                       `{}` is ambiguous.\n\
+                                       Please re-run this command \
+                                       with `-p <spec>` where `<spec>` is one \
+                                       of the following:",
+                                      spec.get_name(), spec);
+                let mut vec = vec![ret, other];
+                vec.extend(ids);
+                minimize(&mut msg, vec, &spec);
+                Err(human(msg))
+            }
+            None => Ok(ret)
+        };
+
+        fn minimize(msg: &mut String,
+                    ids: Vec<&PackageId>,
+                    spec: &PackageIdSpec) {
+            let mut version_cnt = HashMap::new();
+            for id in ids.iter() {
+                let slot = match version_cnt.entry(id.get_version()) {
+                    Occupied(e) => e.into_mut(),
+                    Vacant(e) => e.set(0u),
+                };
+                *slot += 1;
+            }
+            for id in ids.iter() {
+                if version_cnt[id.get_version()] == 1 {
+                    msg.push_str(format!("\n  {}:{}", spec.get_name(),
+                                 id.get_version()).as_slice());
+                } else {
+                    msg.push_str(format!("\n  {}",
+                                         PackageIdSpec::from_package_id(*id))
+                                        .as_slice());
+                }
+            }
+        }
+    }
+
+    pub fn features(&self, pkg: &PackageId) -> Option<&HashSet<String>> {
+        self.features.find(pkg)
+    }
+}
+
+impl fmt::Show for Resolve {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        self.graph.fmt(fmt)
+    }
+}
+
+struct Context<'a, R:'a> {
+    registry: &'a mut R,
+    resolve: Resolve,
+    // cycle detection
+    visited: HashSet<PackageId>,
+
+    // Try not to re-resolve too much
+    resolved: HashMap<PackageId, HashSet<String>>,
+
+    // Eventually, we will have smarter logic for checking for conflicts in the
+    // resolve, but without the registry, conflicts should not exist in
+    // practice, so this is just a sanity check.
+    seen: HashMap<(String, SourceId), semver::Version>,
+}
+
+impl<'a, R: Registry> Context<'a, R> {
+    fn new(registry: &'a mut R, root: PackageId) -> Context<'a, R> {
+        Context {
+            registry: registry,
+            resolve: Resolve::new(root),
+            seen: HashMap::new(),
+            visited: HashSet::new(),
+            resolved: HashMap::new(),
+        }
+    }
+}
+
+/// Builds the list of all packages required to build the first argument.
+pub fn resolve<R: Registry>(summary: &Summary, method: ResolveMethod,
+                            registry: &mut R) -> CargoResult<Resolve> {
+    log!(5, "resolve; summary={}", summary);
+    let _p = profile::start(format!("resolving: {}", summary));
+
+    let mut context = Context::new(registry, summary.get_package_id().clone());
+    context.seen.insert((summary.get_name().to_string(),
+                         summary.get_source_id().clone()),
+                        summary.get_version().clone());
+    try!(resolve_deps(summary, method, &mut context));
+    log!(5, "  result={}", context.resolve);
+    Ok(context.resolve)
+}
+
+fn resolve_deps<'a, R: Registry>(parent: &Summary,
+                                 method: ResolveMethod,
+                                 ctx: &mut Context<'a, R>)
+                                 -> CargoResult<()> {
+    let (deps, features) = try!(resolve_features(parent, method, ctx));
+
+    // Recursively resolve all dependencies
+    for &dep in deps.iter() {
+        if !match ctx.resolved.entry(parent.get_package_id().clone()) {
+            Occupied(entry) => entry.into_mut(),
+            Vacant(entry) => entry.set(HashSet::new()),
+        }.insert(dep.get_name().to_string()) {
+            continue
+        }
+
+        let pkgs = try!(ctx.registry.query(dep));
+        if pkgs.is_empty() {
+            return Err(human(format!("No package named `{}` found \
+                                      (required by `{}`).\n\
+                                      Location searched: {}\n\
+                                      Version required: {}",
+                                     dep.get_name(),
+                                     parent.get_package_id().get_name(),
+                                     dep.get_source_id(),
+                                     dep.get_version_req())));
+        } else if pkgs.len() > 1 {
+            return Err(internal(format!("At the moment, Cargo only supports a \
+                single source for a particular package name ({}).", dep)));
+        }
+
+        let summary = &pkgs[0];
+        let name = summary.get_name().to_string();
+        let source_id = summary.get_source_id().clone();
+        let version = summary.get_version();
+
+        ctx.resolve.graph.link(parent.get_package_id().clone(),
+                               summary.get_package_id().clone());
+
+        let found = match ctx.seen.find(&(name.clone(), source_id.clone())) {
+            Some(v) if v == version => true,
+            Some(..) => {
+                return Err(human(format!("Cargo found multiple copies of {} in \
+                                          {}. This is not currently supported",
+                                         summary.get_name(),
+                                         summary.get_source_id())));
+            }
+            None => false,
+        };
+        if !found {
+            ctx.seen.insert((name, source_id), version.clone());
+            ctx.resolve.graph.add(summary.get_package_id().clone(), []);
+        }
+
+        // Dependency graphs are required to be a DAG. Non-transitive
+        // dependencies (dev-deps), however, can never introduce a cycle, so we
+        // skip them.
+        if dep.is_transitive() &&
+           !ctx.visited.insert(summary.get_package_id().clone()) {
+            return Err(human(format!("Cyclic package dependency: package `{}` \
+                                      depends on itself",
+                                     summary.get_package_id())))
+        }
+
+        // The list of enabled features for this dependency are both those
+        // listed in the dependency itself as well as any of our own features
+        // which enabled a feature of this package.
+        let features = features.find_equiv(&dep.get_name())
+                               .map(|v| v.as_slice())
+                               .unwrap_or(&[]);
+        try!(resolve_deps(summary,
+                          ResolveRequired(false, features,
+                                          dep.uses_default_features()),
+                          ctx));
+        if dep.is_transitive() {
+            ctx.visited.remove(summary.get_package_id());
+        }
+    }
+
+    Ok(())
+}
+
+fn resolve_features<'a, R>(parent: &'a Summary, method: ResolveMethod,
+                           ctx: &mut Context<R>)
+                           -> CargoResult<(Vec<&'a Dependency>,
+                                           HashMap<&'a str, Vec<String>>)> {
+    let dev_deps = match method {
+        ResolveEverything => true,
+        ResolveRequired(dev_deps, _, _) => dev_deps,
+    };
+
+    // First, filter by dev-dependencies
+    let deps = parent.get_dependencies();
+    let deps = deps.iter().filter(|d| d.is_transitive() || dev_deps);
+
+    // Second, weed out optional dependencies, but not those required
+    let (mut feature_deps, used_features) = try!(build_features(parent, method));
+    let mut ret = HashMap::new();
+    let deps = deps.filter(|d| {
+        !d.is_optional() || feature_deps.contains_key_equiv(&d.get_name())
+    }).collect::<Vec<&Dependency>>();
+
+    // Next, sanitize all requested features by whitelisting all the requested
+    // features that correspond to optional dependencies
+    for dep in deps.iter() {
+        let mut base = feature_deps.pop_equiv(&dep.get_name())
+                                   .unwrap_or(Vec::new());
+        for feature in dep.get_features().iter() {
+            base.push(feature.clone());
+            if feature.as_slice().contains("/") {
+                return Err(human(format!("features in dependencies \
+                                          cannot enable features in \
+                                          other dependencies: `{}`",
+                                         feature)));
+            }
+        }
+        ret.insert(dep.get_name(), base);
+    }
+
+    // All features can only point to optional dependencies, in which case they
+    // should have all been weeded out by the above iteration. Any remaining
+    // features are bugs in that the package does not actually have those
+    // features.
+    if feature_deps.len() > 0 {
+        let unknown = feature_deps.keys().map(|s| s.as_slice())
+                                  .collect::<Vec<&str>>();
+        if unknown.len() > 0 {
+            let features = unknown.connect(", ");
+            return Err(human(format!("Package `{}` does not have these features: \
+                                      `{}`", parent.get_package_id(), features)))
+        }
+    }
+
+    // Record what list of features is active for this package.
+    {
+        let pkgid = parent.get_package_id().clone();
+        match ctx.resolve.features.entry(pkgid) {
+            Occupied(entry) => entry.into_mut(),
+            Vacant(entry) => entry.set(HashSet::new()),
+        }.extend(used_features.into_iter());
+    }
+
+    Ok((deps, ret))
+}
+
+// Returns a pair of (feature dependencies, all used features)
+//
+// The feature dependencies map is a mapping of package name to list of features
+// enabled. Each package should be enabled, and each package should have the
+// specified set of features enabled.
+//
+// The all used features set is the set of features which this local package had
+// enabled, which is later used when compiling to instruct the code what
+// features were enabled.
+fn build_features(s: &Summary, method: ResolveMethod)
+                  -> CargoResult<(HashMap<String, Vec<String>>, HashSet<String>)> {
+    let mut deps = HashMap::new();
+    let mut used = HashSet::new();
+    let mut visited = HashSet::new();
+    match method {
+        ResolveEverything => {
+            for key in s.get_features().keys() {
+                try!(add_feature(s, key.as_slice(), &mut deps, &mut used,
+                                 &mut visited));
+            }
+        }
+        ResolveRequired(_, requested_features, _) =>  {
+            for feat in requested_features.iter() {
+                try!(add_feature(s, feat.as_slice(), &mut deps, &mut used,
+                                 &mut visited));
+            }
+        }
+    }
+    match method {
+        ResolveEverything | ResolveRequired(_, _, true) => {
+            if s.get_features().find_equiv(&"default").is_some() &&
+               !visited.contains_equiv(&"default") {
+                try!(add_feature(s, "default", &mut deps, &mut used,
+                                 &mut visited));
+            }
+        }
+        _ => {}
+    }
+    return Ok((deps, used));
+
+    fn add_feature(s: &Summary, feat: &str,
+                   deps: &mut HashMap<String, Vec<String>>,
+                   used: &mut HashSet<String>,
+                   visited: &mut HashSet<String>) -> CargoResult<()> {
+        if feat.is_empty() { return Ok(()) }
+
+        // If this feature is of the form `foo/bar`, then we just lookup package
+        // `foo` and enable its feature `bar`. Otherwise this feature is of the
+        // form `foo` and we need to recurse to enable the feature `foo` for our
+        // own package, which may end up enabling more features or just enabling
+        // a dependency.
+        let mut parts = feat.splitn(1, '/');
+        let feat_or_package = parts.next().unwrap();
+        match parts.next() {
+            Some(feat) => {
+                let package = feat_or_package;
+                match deps.entry(package.to_string()) {
+                    Occupied(e) => e.into_mut(),
+                    Vacant(e) => e.set(Vec::new()),
+                }.push(feat.to_string());
+            }
+            None => {
+                let feat = feat_or_package;
+                if !visited.insert(feat.to_string()) {
+                    return Err(human(format!("Cyclic feature dependency: \
+                                              feature `{}` depends on itself",
+                                              feat)))
+                }
+                used.insert(feat.to_string());
+                match s.get_features().find_equiv(&feat) {
+                    Some(recursive) => {
+                        for f in recursive.iter() {
+                            try!(add_feature(s, f.as_slice(), deps, used,
+                                             visited));
+                        }
+                    }
+                    None => {
+                        match deps.entry(feat.to_string()) {
+                            Occupied(..) => {} // already activated
+                            Vacant(e) => { e.set(Vec::new()); }
+                        }
+                    }
+                }
+                visited.remove(&feat.to_string());
+            }
+        }
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::collections::HashMap;
+
+    use hamcrest::{assert_that, equal_to, contains};
+
+    use core::source::{SourceId, RegistryKind, GitKind};
+    use core::{Dependency, PackageId, Summary, Registry};
+    use util::{CargoResult, ToUrl};
+
+    fn resolve<R: Registry>(pkg: PackageId, deps: Vec<Dependency>,
+                            registry: &mut R)
+                            -> CargoResult<Vec<PackageId>> {
+        let summary = Summary::new(pkg, deps, HashMap::new()).unwrap();
+        let method = super::ResolveEverything;
+        Ok(try!(super::resolve(&summary, method,
+                               registry)).iter().map(|p| p.clone()).collect())
+    }
+
+    trait ToDep {
+        fn to_dep(self) -> Dependency;
+    }
+
+    impl ToDep for &'static str {
+        fn to_dep(self) -> Dependency {
+            let url = "http://example.com".to_url().unwrap();
+            let source_id = SourceId::new(RegistryKind, url);
+            Dependency::parse(self, Some("1.0.0"), &source_id).unwrap()
+        }
+    }
+
+    impl ToDep for Dependency {
+        fn to_dep(self) -> Dependency {
+            self
+        }
+    }
+
+    macro_rules! pkg(
+        ($name:expr => $($deps:expr),+) => ({
+            let d: Vec<Dependency> = vec!($($deps.to_dep()),+);
+
+            Summary::new(PackageId::new($name, "1.0.0", &registry_loc()).unwrap(),
+                         d, HashMap::new()).unwrap()
+        });
+
+        ($name:expr) => (
+            Summary::new(PackageId::new($name, "1.0.0", &registry_loc()).unwrap(),
+                         Vec::new(), HashMap::new()).unwrap()
+        )
+    )
+
+    fn registry_loc() -> SourceId {
+        let remote = "http://example.com".to_url().unwrap();
+        SourceId::new(RegistryKind, remote)
+    }
+
+    fn pkg(name: &str) -> Summary {
+        Summary::new(pkg_id(name), Vec::new(), HashMap::new()).unwrap()
+    }
+
+    fn pkg_id(name: &str) -> PackageId {
+        PackageId::new(name, "1.0.0", &registry_loc()).unwrap()
+    }
+
+    fn pkg_id_loc(name: &str, loc: &str) -> PackageId {
+        let remote = loc.to_url();
+        let source_id = SourceId::new(GitKind("master".to_string()),
+                                      remote.unwrap());
+
+        PackageId::new(name, "1.0.0", &source_id).unwrap()
+    }
+
+    fn pkg_loc(name: &str, loc: &str) -> Summary {
+        Summary::new(pkg_id_loc(name, loc), Vec::new(), HashMap::new()).unwrap()
+    }
+
+    fn dep(name: &str) -> Dependency {
+        let url = "http://example.com".to_url().unwrap();
+        let source_id = SourceId::new(RegistryKind, url);
+        Dependency::parse(name, Some("1.0.0"), &source_id).unwrap()
+    }
+
+    fn dep_loc(name: &str, location: &str) -> Dependency {
+        let url = location.to_url().unwrap();
+        let source_id = SourceId::new(GitKind("master".to_string()), url);
+        Dependency::parse(name, Some("1.0.0"), &source_id).unwrap()
+    }
+
+    fn registry(pkgs: Vec<Summary>) -> Vec<Summary> {
+        pkgs
+    }
+
+    fn names(names: &[&'static str]) -> Vec<PackageId> {
+        names.iter()
+            .map(|name| PackageId::new(*name, "1.0.0", &registry_loc()).unwrap())
+            .collect()
+    }
+
+    fn loc_names(names: &[(&'static str, &'static str)]) -> Vec<PackageId> {
+        names.iter()
+            .map(|&(name, loc)| pkg_id_loc(name, loc)).collect()
+    }
+
+    #[test]
+    pub fn test_resolving_empty_dependency_list() {
+        let res = resolve(pkg_id("root"), Vec::new(),
+                          &mut registry(vec!())).unwrap();
+
+        assert_that(&res, equal_to(&names(["root"])));
+    }
+
+    #[test]
+    pub fn test_resolving_only_package() {
+        let mut reg = registry(vec!(pkg("foo")));
+        let res = resolve(pkg_id("root"), vec![dep("foo")], &mut reg);
+
+        assert_that(&res.unwrap(), contains(names(["root", "foo"])).exactly());
+    }
+
+    #[test]
+    pub fn test_resolving_one_dep() {
+        let mut reg = registry(vec!(pkg("foo"), pkg("bar")));
+        let res = resolve(pkg_id("root"), vec![dep("foo")], &mut reg);
+
+        assert_that(&res.unwrap(), contains(names(["root", "foo"])).exactly());
+    }
+
+    #[test]
+    pub fn test_resolving_multiple_deps() {
+        let mut reg = registry(vec!(pkg!("foo"), pkg!("bar"), pkg!("baz")));
+        let res = resolve(pkg_id("root"), vec![dep("foo"), dep("baz")],
+                          &mut reg).unwrap();
+
+        assert_that(&res, contains(names(["root", "foo", "baz"])).exactly());
+    }
+
+    #[test]
+    pub fn test_resolving_transitive_deps() {
+        let mut reg = registry(vec!(pkg!("foo"), pkg!("bar" => "foo")));
+        let res = resolve(pkg_id("root"), vec![dep("bar")], &mut reg).unwrap();
+
+        assert_that(&res, contains(names(["root", "foo", "bar"])));
+    }
+
+    #[test]
+    pub fn test_resolving_common_transitive_deps() {
+        let mut reg = registry(vec!(pkg!("foo" => "bar"), pkg!("bar")));
+        let res = resolve(pkg_id("root"), vec![dep("foo"), dep("bar")],
+                          &mut reg).unwrap();
+
+        assert_that(&res, contains(names(["root", "foo", "bar"])));
+    }
+
+    #[test]
+    pub fn test_resolving_with_same_name() {
+        let list = vec![pkg_loc("foo", "http://first.example.com"),
+                        pkg_loc("bar", "http://second.example.com")];
+
+        let mut reg = registry(list);
+        let res = resolve(pkg_id("root"),
+                          vec![dep_loc("foo", "http://first.example.com"),
+                               dep_loc("bar", "http://second.example.com")],
+                          &mut reg);
+
+        let mut names = loc_names([("foo", "http://first.example.com"),
+                                   ("bar", "http://second.example.com")]);
+
+        names.push(pkg_id("root"));
+
+        assert_that(&res.unwrap(), contains(names).exactly());
+    }
+
+    #[test]
+    pub fn test_resolving_with_dev_deps() {
+        let mut reg = registry(vec!(
+            pkg!("foo" => "bar", dep("baz").transitive(false)),
+            pkg!("baz" => "bat", dep("bam").transitive(false)),
+            pkg!("bar"),
+            pkg!("bat")
+        ));
+
+        let res = resolve(pkg_id("root"),
+                          vec![dep("foo"), dep("baz").transitive(false)],
+                          &mut reg).unwrap();
+
+        assert_that(&res, contains(names(["root", "foo", "bar", "baz"])));
+    }
+}
+